home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.1.5 / components / nsLoginManager.js < prev    next >
Encoding:
Text File  |  2009-11-08  |  50.1 KB  |  1,390 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *  Ehsan Akhgari <ehsan.akhgari@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38.  
  39. const Cc = Components.classes;
  40. const Ci = Components.interfaces;
  41.  
  42. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  43.  
  44. function LoginManager() {
  45.     this.init();
  46. }
  47.  
  48. LoginManager.prototype = {
  49.  
  50.     classDescription: "LoginManager",
  51.     contractID: "@mozilla.org/login-manager;1",
  52.     classID: Components.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
  53.     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManager,
  54.                                             Ci.nsISupportsWeakReference,
  55.                                             Ci.nsILoginManager_MOZILLA_1_9_1,
  56.                                             Ci.nsIClassInfo]),
  57.  
  58.     /* ---------- extra requirements for nsIClassInfo ---------- */
  59.     getInterfaces: function(countRef) {
  60.         let interfaces = [Ci.nsILoginManager, Ci.nsISupportsWeakReference,
  61.                           Ci.nsILoginManager_MOZILLA_1_9_1, Ci.nsIClassInfo];
  62.         countRef.value = interfaces.length;
  63.         return interfaces;
  64.     },
  65.     getHelperForLanguage: function (language) null,
  66.     implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  67.     flags: Ci.nsIClassInfo.SINGLETON,
  68.  
  69.  
  70.     /* ---------- private memebers ---------- */
  71.  
  72.  
  73.     __logService : null, // Console logging service, used for debugging.
  74.     get _logService() {
  75.         if (!this.__logService)
  76.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  77.                                 getService(Ci.nsIConsoleService);
  78.         return this.__logService;
  79.     },
  80.  
  81.  
  82.     __ioService: null, // IO service for string -> nsIURI conversion
  83.     get _ioService() {
  84.         if (!this.__ioService)
  85.             this.__ioService = Cc["@mozilla.org/network/io-service;1"].
  86.                                getService(Ci.nsIIOService);
  87.         return this.__ioService;
  88.     },
  89.  
  90.  
  91.     __formFillService : null, // FormFillController, for username autocompleting
  92.     get _formFillService() {
  93.         if (!this.__formFillService)
  94.             this.__formFillService =
  95.                             Cc["@mozilla.org/satchel/form-fill-controller;1"].
  96.                             getService(Ci.nsIFormFillController);
  97.         return this.__formFillService;
  98.     },
  99.  
  100.  
  101.     __observerService : null, // Observer Service, for notifications
  102.     get _observerService() {
  103.         if (!this.__observerService)
  104.             this.__observerService = Cc["@mozilla.org/observer-service;1"].
  105.                                      getService(Ci.nsIObserverService);
  106.         return this.__observerService;
  107.     },
  108.  
  109.  
  110.     __storage : null, // Storage component which contains the saved logins
  111.     get _storage() {
  112.         if (!this.__storage) {
  113.  
  114.             var contractID = "@mozilla.org/login-manager/storage/mozStorage;1";
  115.             try {
  116.                 var catMan = Cc["@mozilla.org/categorymanager;1"].
  117.                              getService(Ci.nsICategoryManager);
  118.                 contractID = catMan.getCategoryEntry("login-manager-storage",
  119.                                                      "nsILoginManagerStorage");
  120.                 this.log("Found alternate nsILoginManagerStorage with " +
  121.                          "contract ID: " + contractID);
  122.             } catch (e) {
  123.                 this.log("No alternate nsILoginManagerStorage registered");
  124.             }
  125.  
  126.             this.__storage = Cc[contractID].
  127.                              createInstance(Ci.nsILoginManagerStorage);
  128.             try {
  129.                 this.__storage.init();
  130.             } catch (e) {
  131.                 this.log("Initialization of storage component failed: " + e);
  132.                 this.__storage = null;
  133.             }
  134.         }
  135.  
  136.         return this.__storage;
  137.     },
  138.  
  139.  
  140.     // Private Browsing Service
  141.     // If the service is not available, null will be returned.
  142.     __privateBrowsingService : undefined,
  143.     get _privateBrowsingService() {
  144.         if (this.__privateBrowsingService == undefined) {
  145.             if ("@mozilla.org/privatebrowsing;1" in Cc)
  146.                 this.__privateBrowsingService = Cc["@mozilla.org/privatebrowsing;1"].
  147.                                                 getService(Ci.nsIPrivateBrowsingService);
  148.             else
  149.                 this.__privateBrowsingService = null;
  150.         }
  151.         return this.__privateBrowsingService;
  152.     },
  153.  
  154.  
  155.     // Whether we are in private browsing mode
  156.     get _inPrivateBrowsing() {
  157.         var pbSvc = this._privateBrowsingService;
  158.         if (pbSvc)
  159.             return pbSvc.privateBrowsingEnabled;
  160.         else
  161.             return false;
  162.     },
  163.  
  164.     _prefBranch  : null, // Preferences service
  165.     _nsLoginInfo : null, // Constructor for nsILoginInfo implementation
  166.  
  167.     _remember : true,  // mirrors signon.rememberSignons preference
  168.     _debug    : false, // mirrors signon.debug
  169.  
  170.  
  171.     /*
  172.      * init
  173.      *
  174.      * Initialize the Login Manager. Automatically called when service
  175.      * is created.
  176.      *
  177.      * Note: Service created in /browser/base/content/browser.js,
  178.      *       delayedStartup()
  179.      */
  180.     init : function () {
  181.  
  182.         // Cache references to current |this| in utility objects
  183.         this._webProgressListener._domEventListener = this._domEventListener;
  184.         this._webProgressListener._pwmgr = this;
  185.         this._domEventListener._pwmgr    = this;
  186.         this._observer._pwmgr            = this;
  187.  
  188.         // Preferences. Add observer so we get notified of changes.
  189.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  190.                            getService(Ci.nsIPrefService).getBranch("signon.");
  191.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  192.         this._prefBranch.addObserver("", this._observer, false);
  193.  
  194.         // Get current preference values.
  195.         this._debug = this._prefBranch.getBoolPref("debug");
  196.  
  197.         this._remember = this._prefBranch.getBoolPref("rememberSignons");
  198.  
  199.  
  200.         // Get constructor for nsILoginInfo
  201.         this._nsLoginInfo = new Components.Constructor(
  202.             "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
  203.  
  204.  
  205.         // Form submit observer checks forms for new logins and pw changes.
  206.         this._observerService.addObserver(this._observer, "earlyformsubmit", false);
  207.         this._observerService.addObserver(this._observer, "xpcom-shutdown", false);
  208.  
  209.         // WebProgressListener for getting notification of new doc loads.
  210.         var progress = Cc["@mozilla.org/docloaderservice;1"].
  211.                        getService(Ci.nsIWebProgress);
  212.         progress.addProgressListener(this._webProgressListener,
  213.                                      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
  214.  
  215.  
  216.     },
  217.  
  218.  
  219.     /*
  220.      * log
  221.      *
  222.      * Internal function for logging debug messages to the Error Console window
  223.      */
  224.     log : function (message) {
  225.         if (!this._debug)
  226.             return;
  227.         dump("Login Manager: " + message + "\n");
  228.         this._logService.logStringMessage("Login Manager: " + message);
  229.     },
  230.  
  231.  
  232.     /* ---------- Utility objects ---------- */
  233.  
  234.  
  235.     /*
  236.      * _observer object
  237.      *
  238.      * Internal utility object, implements the nsIObserver interface.
  239.      * Used to receive notification for: form submission, preference changes.
  240.      */
  241.     _observer : {
  242.         _pwmgr : null,
  243.  
  244.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, 
  245.                                                 Ci.nsIFormSubmitObserver,
  246.                                                 Ci.nsISupportsWeakReference]),
  247.  
  248.  
  249.         // nsFormSubmitObserver
  250.         notify : function (formElement, aWindow, actionURI) {
  251.             this._pwmgr.log("observer notified for form submission.");
  252.  
  253.             // We're invoked before the content's |onsubmit| handlers, so we
  254.             // can grab form data before it might be modified (see bug 257781).
  255.  
  256.             try {
  257.                 this._pwmgr._onFormSubmit(formElement);
  258.             } catch (e) {
  259.                 this._pwmgr.log("Caught error in onFormSubmit: " + e);
  260.             }
  261.  
  262.             return true; // Always return true, or form submit will be canceled.
  263.         },
  264.  
  265.         // nsObserver
  266.         observe : function (subject, topic, data) {
  267.  
  268.             if (topic == "nsPref:changed") {
  269.                 var prefName = data;
  270.                 this._pwmgr.log("got change to " + prefName + " preference");
  271.  
  272.                 if (prefName == "debug") {
  273.                     this._pwmgr._debug = 
  274.                         this._pwmgr._prefBranch.getBoolPref("debug");
  275.                 } else if (prefName == "rememberSignons") {
  276.                     this._pwmgr._remember =
  277.                         this._pwmgr._prefBranch.getBoolPref("rememberSignons");
  278.                 } else {
  279.                     this._pwmgr.log("Oops! Pref not handled, change ignored.");
  280.                 }
  281.             } else if (topic == "xpcom-shutdown") {
  282.                 for (let i in this._pwmgr) {
  283.                   try {
  284.                     this._pwmgr[i] = null;
  285.                   } catch(ex) {}
  286.                 }
  287.                 this._pwmgr = null;
  288.             } else {
  289.                 this._pwmgr.log("Oops! Unexpected notification: " + topic);
  290.             }
  291.         }
  292.     },
  293.  
  294.  
  295.     /*
  296.      * _webProgressListener object
  297.      *
  298.      * Internal utility object, implements nsIWebProgressListener interface.
  299.      * This is attached to the document loader service, so we get
  300.      * notifications about all page loads.
  301.      */
  302.     _webProgressListener : {
  303.         _pwmgr : null,
  304.         _domEventListener : null,
  305.  
  306.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  307.                                                 Ci.nsISupportsWeakReference]),
  308.  
  309.  
  310.         onStateChange : function (aWebProgress, aRequest,
  311.                                   aStateFlags,  aStatus) {
  312.  
  313.             // STATE_START is too early, doc is still the old page.
  314.             if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_TRANSFERRING))
  315.                 return;
  316.  
  317.             if (!this._pwmgr._remember)
  318.                 return;
  319.  
  320.             var domWin = aWebProgress.DOMWindow;
  321.             var domDoc = domWin.document;
  322.  
  323.             // Only process things which might have HTML forms.
  324.             if (!(domDoc instanceof Ci.nsIDOMHTMLDocument))
  325.                 return;
  326.  
  327.             this._pwmgr.log("onStateChange accepted: req = " +
  328.                             (aRequest ?  aRequest.name : "(null)") +
  329.                             ", flags = 0x" + aStateFlags.toString(16));
  330.  
  331.             // Fastback doesn't fire DOMContentLoaded, so process forms now.
  332.             if (aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) {
  333.                 this._pwmgr.log("onStateChange: restoring document");
  334.                 return this._pwmgr._fillDocument(domDoc);
  335.             }
  336.  
  337.             // Add event listener to process page when DOM is complete.
  338.             domDoc.addEventListener("DOMContentLoaded",
  339.                                     this._domEventListener, false);
  340.             return;
  341.         },
  342.  
  343.         // stubs for the nsIWebProgressListener interfaces which we don't use.
  344.         onProgressChange : function() { throw "Unexpected onProgressChange"; },
  345.         onLocationChange : function() { throw "Unexpected onLocationChange"; },
  346.         onStatusChange   : function() { throw "Unexpected onStatusChange";   },
  347.         onSecurityChange : function() { throw "Unexpected onSecurityChange"; }
  348.     },
  349.  
  350.  
  351.     /*
  352.      * _domEventListener object
  353.      *
  354.      * Internal utility object, implements nsIDOMEventListener
  355.      * Used to catch certain DOM events needed to properly implement form fill.
  356.      */
  357.     _domEventListener : {
  358.         _pwmgr : null,
  359.  
  360.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
  361.                                                 Ci.nsISupportsWeakReference]),
  362.  
  363.  
  364.         handleEvent : function (event) {
  365.             if (!event.isTrusted)
  366.                 return;
  367.  
  368.             this._pwmgr.log("domEventListener: got event " + event.type);
  369.  
  370.             switch (event.type) {
  371.                 case "DOMContentLoaded":
  372.                     this._pwmgr._fillDocument(event.target);
  373.                     return;
  374.  
  375.                 case "DOMAutoComplete":
  376.                 case "blur":
  377.                     var acInputField = event.target;
  378.                     var acForm = acInputField.form;
  379.  
  380.                     // If the username is blank, bail out now -- we don't want
  381.                     // fillForm() to try filling in a login without a username
  382.                     // to filter on (bug 471906).
  383.                     if (!acInputField.value)
  384.                         return;
  385.  
  386.                     // Make sure the username field fillForm will use is the
  387.                     // same field as the autocomplete was activated on. If
  388.                     // not, the DOM has been altered and we'll just give up.
  389.                     var [usernameField, passwordField, ignored] =
  390.                         this._pwmgr._getFormFields(acForm, false);
  391.                     if (usernameField == acInputField && passwordField) {
  392.                         // Clobber any existing password.
  393.                         passwordField.value = "";
  394.                         this._pwmgr._fillForm(acForm, true, true, null);
  395.                     } else {
  396.                         this._pwmgr.log("Oops, form changed before AC invoked");
  397.                     }
  398.                     return;
  399.  
  400.                 default:
  401.                     this._pwmgr.log("Oops! This event unexpected.");
  402.                     return;
  403.             }
  404.         }
  405.     },
  406.  
  407.  
  408.  
  409.  
  410.     /* ---------- Primary Public interfaces ---------- */
  411.  
  412.  
  413.  
  414.  
  415.     /*
  416.      * addLogin
  417.      *
  418.      * Add a new login to login storage.
  419.      */
  420.     addLogin : function (login) {
  421.         // Sanity check the login
  422.         if (login.hostname == null || login.hostname.length == 0)
  423.             throw "Can't add a login with a null or empty hostname.";
  424.  
  425.         // For logins w/o a username, set to "", not null.
  426.         if (login.username == null)
  427.             throw "Can't add a login with a null username.";
  428.  
  429.         if (login.password == null || login.password.length == 0)
  430.             throw "Can't add a login with a null or empty password.";
  431.  
  432.         if (login.formSubmitURL || login.formSubmitURL == "") {
  433.             // We have a form submit URL. Can't have a HTTP realm.
  434.             if (login.httpRealm != null)
  435.                 throw "Can't add a login with both a httpRealm and formSubmitURL.";
  436.         } else if (login.httpRealm) {
  437.             // We have a HTTP realm. Can't have a form submit URL.
  438.             if (login.formSubmitURL != null)
  439.                 throw "Can't add a login with both a httpRealm and formSubmitURL.";
  440.         } else {
  441.             // Need one or the other!
  442.             throw "Can't add a login without a httpRealm or formSubmitURL.";
  443.         }
  444.  
  445.  
  446.         // Look for an existing entry.
  447.         var logins = this.findLogins({}, login.hostname, login.formSubmitURL,
  448.                                      login.httpRealm);
  449.  
  450.         if (logins.some(function(l) login.matches(l, true)))
  451.             throw "This login already exists.";
  452.  
  453.         this.log("Adding login: " + login);
  454.         return this._storage.addLogin(login);
  455.     },
  456.  
  457.  
  458.     /*
  459.      * removeLogin
  460.      *
  461.      * Remove the specified login from the stored logins.
  462.      */
  463.     removeLogin : function (login) {
  464.         this.log("Removing login: " + login);
  465.         return this._storage.removeLogin(login);
  466.     },
  467.  
  468.  
  469.     /*
  470.      * modifyLogin
  471.      *
  472.      * Change the specified login to match the new login.
  473.      */
  474.     modifyLogin : function (oldLogin, newLogin) {
  475.         this.log("Modifying oldLogin: " + oldLogin + " newLogin: " + newLogin);
  476.         return this._storage.modifyLogin(oldLogin, newLogin);
  477.     },
  478.  
  479.  
  480.     /*
  481.      * getAllLogins
  482.      *
  483.      * Get a dump of all stored logins. Used by the login manager UI.
  484.      *
  485.      * |count| is only needed for XPCOM.
  486.      *
  487.      * Returns an array of logins. If there are no logins, the array is empty.
  488.      */
  489.     getAllLogins : function (count) {
  490.         this.log("Getting a list of all logins");
  491.         return this._storage.getAllLogins(count);
  492.     },
  493.  
  494.  
  495.     /*
  496.      * removeAllLogins
  497.      *
  498.      * Remove all stored logins.
  499.      */
  500.     removeAllLogins : function () {
  501.         this.log("Removing all logins");
  502.         this._storage.removeAllLogins();
  503.     },
  504.  
  505.     /*
  506.      * getAllDisabledHosts
  507.      *
  508.      * Get a list of all hosts for which logins are disabled.
  509.      *
  510.      * |count| is only needed for XPCOM.
  511.      *
  512.      * Returns an array of disabled logins. If there are no disabled logins,
  513.      * the array is empty.
  514.      */
  515.     getAllDisabledHosts : function (count) {
  516.         this.log("Getting a list of all disabled hosts");
  517.         return this._storage.getAllDisabledHosts(count);
  518.     },
  519.  
  520.  
  521.     /*
  522.      * findLogins
  523.      *
  524.      * Search for the known logins for entries matching the specified criteria.
  525.      */
  526.     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
  527.         this.log("Searching for logins matching host: " + hostname +
  528.             ", formSubmitURL: " + formSubmitURL + ", httpRealm: " + httpRealm);
  529.  
  530.         return this._storage.findLogins(count, hostname, formSubmitURL,
  531.                                         httpRealm);
  532.     },
  533.  
  534.  
  535.     /*
  536.      * searchLogins
  537.      *
  538.      * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
  539.      * JavaScript object and decrypt the results.
  540.      *
  541.      * Returns an array of decrypted nsILoginInfo.
  542.      */
  543.     searchLogins : function(count, matchData) {
  544.        this.log("Searching for logins");
  545.  
  546.         return this._storage.searchLogins(count, matchData);
  547.     },
  548.  
  549.  
  550.     /*
  551.      * countLogins
  552.      *
  553.      * Search for the known logins for entries matching the specified criteria,
  554.      * returns only the count.
  555.      */
  556.     countLogins : function (hostname, formSubmitURL, httpRealm) {
  557.         this.log("Counting logins matching host: " + hostname +
  558.             ", formSubmitURL: " + formSubmitURL + ", httpRealm: " + httpRealm);
  559.  
  560.         return this._storage.countLogins(hostname, formSubmitURL, httpRealm);
  561.     },
  562.  
  563.  
  564.     /*
  565.      * getLoginSavingEnabled
  566.      *
  567.      * Check to see if user has disabled saving logins for the host.
  568.      */
  569.     getLoginSavingEnabled : function (host) {
  570.         this.log("Checking if logins to " + host + " can be saved.");
  571.         if (!this._remember)
  572.             return false;
  573.  
  574.         return this._storage.getLoginSavingEnabled(host);
  575.     },
  576.  
  577.  
  578.     /*
  579.      * setLoginSavingEnabled
  580.      *
  581.      * Enable or disable storing logins for the specified host.
  582.      */
  583.     setLoginSavingEnabled : function (hostname, enabled) {
  584.         // Nulls won't round-trip with getAllDisabledHosts().
  585.         if (hostname.indexOf("\0") != -1)
  586.             throw "Invalid hostname";
  587.  
  588.         this.log("Saving logins for " + hostname + " enabled? " + enabled);
  589.         return this._storage.setLoginSavingEnabled(hostname, enabled);
  590.     },
  591.  
  592.  
  593.     /*
  594.      * autoCompleteSearch
  595.      *
  596.      * Yuck. This is called directly by satchel:
  597.      * nsFormFillController::StartSearch()
  598.      * [toolkit/components/satchel/src/nsFormFillController.cpp]
  599.      *
  600.      * We really ought to have a simple way for code to register an
  601.      * auto-complete provider, and not have satchel calling pwmgr directly.
  602.      */
  603.     autoCompleteSearch : function (aSearchString, aPreviousResult, aElement) {
  604.         // aPreviousResult & aResult are nsIAutoCompleteResult,
  605.         // aElement is nsIDOMHTMLInputElement
  606.  
  607.         if (!this._remember)
  608.             return false;
  609.  
  610.         this.log("AutoCompleteSearch invoked. Search is: " + aSearchString);
  611.  
  612.         var result = null;
  613.  
  614.         if (aPreviousResult) {
  615.             this.log("Using previous autocomplete result");
  616.             result = aPreviousResult;
  617.  
  618.             // We have a list of results for a shorter search string, so just
  619.             // filter them further based on the new search string.
  620.             // Count backwards, because result.matchCount is decremented
  621.             // when we remove an entry.
  622.             for (var i = result.matchCount - 1; i >= 0; i--) {
  623.                 var match = result.getValueAt(i);
  624.  
  625.                 // Remove results that are too short, or have different prefix.
  626.                 if (aSearchString.length > match.length ||
  627.                     aSearchString.toLowerCase() !=
  628.                         match.substr(0, aSearchString.length).toLowerCase())
  629.                 {
  630.                     this.log("Removing autocomplete entry '" + match + "'");
  631.                     result.removeValueAt(i, false);
  632.                 }
  633.             }
  634.         } else {
  635.             this.log("Creating new autocomplete search result.");
  636.  
  637.             var doc = aElement.ownerDocument;
  638.             var origin = this._getPasswordOrigin(doc.documentURI);
  639.             var actionOrigin = this._getActionOrigin(aElement.form);
  640.  
  641.             var logins = this.findLogins({}, origin, actionOrigin, null);
  642.             var matchingLogins = [];
  643.  
  644.             // Filter out logins that don't match the search prefix. Also
  645.             // filter logins without a username, since that's confusing to see
  646.             // in the dropdown and we can't autocomplete them anyway.
  647.             for (i = 0; i < logins.length; i++) {
  648.                 var username = logins[i].username.toLowerCase();
  649.                 if (username &&
  650.                     aSearchString.length <= username.length &&
  651.                     aSearchString.toLowerCase() ==
  652.                         username.substr(0, aSearchString.length))
  653.                 {
  654.                     matchingLogins.push(logins[i]);
  655.                 }
  656.             }
  657.             this.log(matchingLogins.length + " autocomplete logins avail.");
  658.             result = new UserAutoCompleteResult(aSearchString, matchingLogins);
  659.         }
  660.  
  661.         return result;
  662.     },
  663.  
  664.  
  665.  
  666.  
  667.     /* ------- Internal methods / callbacks for document integration ------- */
  668.  
  669.  
  670.  
  671.  
  672.     /*
  673.      * _getPasswordFields
  674.      *
  675.      * Returns an array of password field elements for the specified form.
  676.      * If no pw fields are found, or if more than 3 are found, then null
  677.      * is returned.
  678.      *
  679.      * skipEmptyFields can be set to ignore password fields with no value.
  680.      */
  681.     _getPasswordFields : function (form, skipEmptyFields) {
  682.         // Locate the password fields in the form.
  683.         var pwFields = [];
  684.         for (var i = 0; i < form.elements.length; i++) {
  685.             var element = form.elements[i];
  686.             if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
  687.                 element.type != "password")
  688.                 continue;
  689.  
  690.             if (skipEmptyFields && !element.value)
  691.                 continue;
  692.  
  693.             pwFields[pwFields.length] = {
  694.                                             index   : i,
  695.                                             element : element
  696.                                         };
  697.         }
  698.  
  699.         // If too few or too many fields, bail out.
  700.         if (pwFields.length == 0) {
  701.             this.log("(form ignored -- no password fields.)");
  702.             return null;
  703.         } else if (pwFields.length > 3) {
  704.             this.log("(form ignored -- too many password fields. [got " +
  705.                         pwFields.length + "])");
  706.             return null;
  707.         }
  708.  
  709.         return pwFields;
  710.     },
  711.  
  712.  
  713.     /*
  714.      * _getFormFields
  715.      *
  716.      * Returns the username and password fields found in the form.
  717.      * Can handle complex forms by trying to figure out what the
  718.      * relevant fields are.
  719.      *
  720.      * Returns: [usernameField, newPasswordField, oldPasswordField]
  721.      *
  722.      * usernameField may be null.
  723.      * newPasswordField will always be non-null.
  724.      * oldPasswordField may be null. If null, newPasswordField is just
  725.      * "theLoginField". If not null, the form is apparently a
  726.      * change-password field, with oldPasswordField containing the password
  727.      * that is being changed.
  728.      */
  729.     _getFormFields : function (form, isSubmission) {
  730.         var usernameField = null;
  731.  
  732.         // Locate the password field(s) in the form. Up to 3 supported.
  733.         // If there's no password field, there's nothing for us to do.
  734.         var pwFields = this._getPasswordFields(form, isSubmission);
  735.         if (!pwFields)
  736.             return [null, null, null];
  737.  
  738.  
  739.         // Locate the username field in the form by searching backwards
  740.         // from the first passwordfield, assume the first text field is the
  741.         // username. We might not find a username field if the user is
  742.         // already logged in to the site. 
  743.         for (var i = pwFields[0].index - 1; i >= 0; i--) {
  744.             if (form.elements[i].type == "text") {
  745.                 usernameField = form.elements[i];
  746.                 break;
  747.             }
  748.         }
  749.  
  750.         if (!usernameField)
  751.             this.log("(form -- no username field found)");
  752.  
  753.  
  754.         // If we're not submitting a form (it's a page load), there are no
  755.         // password field values for us to use for identifying fields. So,
  756.         // just assume the first password field is the one to be filled in.
  757.         if (!isSubmission || pwFields.length == 1)
  758.             return [usernameField, pwFields[0].element, null];
  759.  
  760.  
  761.         // Try to figure out WTF is in the form based on the password values.
  762.         var oldPasswordField, newPasswordField;
  763.         var pw1 = pwFields[0].element.value;
  764.         var pw2 = pwFields[1].element.value;
  765.         var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
  766.  
  767.         if (pwFields.length == 3) {
  768.             // Look for two identical passwords, that's the new password
  769.  
  770.             if (pw1 == pw2 && pw2 == pw3) {
  771.                 // All 3 passwords the same? Weird! Treat as if 1 pw field.
  772.                 newPasswordField = pwFields[0].element;
  773.                 oldPasswordField = null;
  774.             } else if (pw1 == pw2) {
  775.                 newPasswordField = pwFields[0].element;
  776.                 oldPasswordField = pwFields[2].element;
  777.             } else if (pw2 == pw3) {
  778.                 oldPasswordField = pwFields[0].element;
  779.                 newPasswordField = pwFields[2].element;
  780.             } else  if (pw1 == pw3) {
  781.                 // A bit odd, but could make sense with the right page layout.
  782.                 newPasswordField = pwFields[0].element;
  783.                 oldPasswordField = pwFields[1].element;
  784.             } else {
  785.                 // We can't tell which of the 3 passwords should be saved.
  786.                 this.log("(form ignored -- all 3 pw fields differ)");
  787.                 return [null, null, null];
  788.             }
  789.         } else { // pwFields.length == 2
  790.             if (pw1 == pw2) {
  791.                 // Treat as if 1 pw field
  792.                 newPasswordField = pwFields[0].element;
  793.                 oldPasswordField = null;
  794.             } else {
  795.                 // Just assume that the 2nd password is the new password
  796.                 oldPasswordField = pwFields[0].element;
  797.                 newPasswordField = pwFields[1].element;
  798.             }
  799.         }
  800.  
  801.         return [usernameField, newPasswordField, oldPasswordField];
  802.     },
  803.  
  804.  
  805.     /*
  806.      * _isAutoCompleteDisabled
  807.      *
  808.      * Returns true if the page requests autocomplete be disabled for the
  809.      * specified form input.
  810.      */
  811.     _isAutocompleteDisabled :  function (element) {
  812.         if (element && element.hasAttribute("autocomplete") &&
  813.             element.getAttribute("autocomplete").toLowerCase() == "off")
  814.             return true;
  815.  
  816.         return false;
  817.     },
  818.  
  819.     /*
  820.      * _onFormSubmit
  821.      *
  822.      * Called by the our observer when notified of a form submission.
  823.      * [Note that this happens before any DOM onsubmit handlers are invoked.]
  824.      * Looks for a password change in the submitted form, so we can update
  825.      * our stored password.
  826.      */
  827.     _onFormSubmit : function (form) {
  828.  
  829.         // local helper function
  830.         function getPrompter(aWindow) {
  831.             var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
  832.                               createInstance(Ci.nsILoginManagerPrompter);
  833.             prompterSvc.init(aWindow);
  834.             return prompterSvc;
  835.         }
  836.  
  837.         if (this._inPrivateBrowsing) {
  838.             // We won't do anything in private browsing mode anyway,
  839.             // so there's no need to perform further checks.
  840.             this.log("(form submission ignored in private browsing mode)");
  841.             return;
  842.         }
  843.  
  844.         var doc = form.ownerDocument;
  845.         var win = doc.defaultView;
  846.  
  847.         // If password saving is disabled (globally or for host), bail out now.
  848.         if (!this._remember)
  849.             return;
  850.  
  851.         var hostname      = this._getPasswordOrigin(doc.documentURI);
  852.         var formSubmitURL = this._getActionOrigin(form)
  853.         if (!this.getLoginSavingEnabled(hostname)) {
  854.             this.log("(form submission ignored -- saving is " +
  855.                      "disabled for: " + hostname + ")");
  856.             return;
  857.         }
  858.  
  859.  
  860.         // Get the appropriate fields from the form.
  861.         var [usernameField, newPasswordField, oldPasswordField] =
  862.             this._getFormFields(form, true);
  863.  
  864.         // Need at least 1 valid password field to do anything.
  865.         if (newPasswordField == null)
  866.                 return;
  867.  
  868.         // Check for autocomplete=off attribute. We don't use it to prevent
  869.         // autofilling (for existing logins), but won't save logins when it's
  870.         // present.
  871.         if (this._isAutocompleteDisabled(form) ||
  872.             this._isAutocompleteDisabled(usernameField) ||
  873.             this._isAutocompleteDisabled(newPasswordField) ||
  874.             this._isAutocompleteDisabled(oldPasswordField)) {
  875.                 this.log("(form submission ignored -- autocomplete=off found)");
  876.                 return;
  877.         }
  878.  
  879.  
  880.         var formLogin = new this._nsLoginInfo();
  881.         formLogin.init(hostname, formSubmitURL, null,
  882.                     (usernameField ? usernameField.value : ""),
  883.                     newPasswordField.value,
  884.                     (usernameField ? usernameField.name  : ""),
  885.                     newPasswordField.name);
  886.  
  887.         // If we didn't find a username field, but seem to be changing a
  888.         // password, allow the user to select from a list of applicable
  889.         // logins to update the password for.
  890.         if (!usernameField && oldPasswordField) {
  891.  
  892.             var logins = this.findLogins({}, hostname, formSubmitURL, null);
  893.  
  894.             if (logins.length == 0) {
  895.                 // Could prompt to save this as a new password-only login.
  896.                 // This seems uncommon, and might be wrong, so ignore.
  897.                 this.log("(no logins for this host -- pwchange ignored)");
  898.                 return;
  899.             }
  900.  
  901.             var prompter = getPrompter(win);
  902.  
  903.             if (logins.length == 1) {
  904.                 var oldLogin = logins[0];
  905.                 formLogin.username      = oldLogin.username;
  906.                 formLogin.usernameField = oldLogin.usernameField;
  907.  
  908.                 prompter.promptToChangePassword(oldLogin, formLogin);
  909.             } else {
  910.                 prompter.promptToChangePasswordWithUsernames(
  911.                                     logins, logins.length, formLogin);
  912.             }
  913.  
  914.             return;
  915.         }
  916.  
  917.  
  918.         // Look for an existing login that matches the form login.
  919.         var existingLogin = null;
  920.         var logins = this.findLogins({}, hostname, formSubmitURL, null);
  921.  
  922.         for (var i = 0; i < logins.length; i++) {
  923.             var same, login = logins[i];
  924.  
  925.             // If one login has a username but the other doesn't, ignore
  926.             // the username when comparing and only match if they have the
  927.             // same password. Otherwise, compare the logins and match even
  928.             // if the passwords differ.
  929.             if (!login.username && formLogin.username) {
  930.                 var restoreMe = formLogin.username;
  931.                 formLogin.username = ""; 
  932.                 same = formLogin.matches(login, false);
  933.                 formLogin.username = restoreMe;
  934.             } else if (!formLogin.username && login.username) {
  935.                 formLogin.username = login.username;
  936.                 same = formLogin.matches(login, false);
  937.                 formLogin.username = ""; // we know it's always blank.
  938.             } else {
  939.                 same = formLogin.matches(login, true);
  940.             }
  941.  
  942.             if (same) {
  943.                 existingLogin = login;
  944.                 break;
  945.             }
  946.         }
  947.  
  948.         if (existingLogin) {
  949.             this.log("Found an existing login matching this form submission");
  950.  
  951.             // Change password if needed.
  952.             if (existingLogin.password != formLogin.password) {
  953.                 this.log("...passwords differ, prompting to change.");
  954.                 prompter = getPrompter(win);
  955.                 prompter.promptToChangePassword(existingLogin, formLogin);
  956.             }
  957.  
  958.             return;
  959.         }
  960.  
  961.  
  962.         // Prompt user to save login (via dialog or notification bar)
  963.         prompter = getPrompter(win);
  964.         prompter.promptToSavePassword(formLogin);
  965.     },
  966.  
  967.  
  968.     /*
  969.      * _getPasswordOrigin
  970.      *
  971.      * Get the parts of the URL we want for identification.
  972.      */
  973.     _getPasswordOrigin : function (uriString, allowJS) {
  974.         var realm = "";
  975.         try {
  976.             var uri = this._ioService.newURI(uriString, null, null);
  977.  
  978.             if (allowJS && uri.scheme == "javascript")
  979.                 return "javascript:"
  980.  
  981.             realm = uri.scheme + "://" + uri.host;
  982.  
  983.             // If the URI explicitly specified a port, only include it when
  984.             // it's not the default. (We never want "http://foo.com:80")
  985.             var port = uri.port;
  986.             if (port != -1) {
  987.                 var handler = this._ioService.getProtocolHandler(uri.scheme);
  988.                 if (port != handler.defaultPort)
  989.                     realm += ":" + port;
  990.             }
  991.  
  992.         } catch (e) {
  993.             // bug 159484 - disallow url types that don't support a hostPort.
  994.             // (although we handle "javascript:..." as a special case above.)
  995.             this.log("Couldn't parse origin for " + uriString);
  996.             realm = null;
  997.         }
  998.  
  999.         return realm;
  1000.     },
  1001.  
  1002.     _getActionOrigin : function (form) {
  1003.         var uriString = form.action;
  1004.  
  1005.         // A blank or mission action submits to where it came from.
  1006.         if (uriString == "")
  1007.             uriString = form.baseURI; // ala bug 297761
  1008.  
  1009.         return this._getPasswordOrigin(uriString, true);
  1010.     },
  1011.  
  1012.  
  1013.     /*
  1014.      * _fillDocument
  1015.      *
  1016.      * Called when a page has loaded. For each form in the document,
  1017.      * we check to see if it can be filled with a stored login.
  1018.      */
  1019.     _fillDocument : function (doc) {
  1020.         var forms = doc.forms;
  1021.         if (!forms || forms.length == 0)
  1022.             return;
  1023.  
  1024.         var formOrigin = this._getPasswordOrigin(doc.documentURI);
  1025.  
  1026.         // If there are no logins for this site, bail out now.
  1027.         if (!this.countLogins(formOrigin, "", null))
  1028.             return;
  1029.  
  1030.         this.log("fillDocument processing " + forms.length +
  1031.                  " forms on " + doc.documentURI);
  1032.  
  1033.         var autofillForm = !this._inPrivateBrowsing &&
  1034.                            this._prefBranch.getBoolPref("autofillForms");
  1035.         var previousActionOrigin = null;
  1036.         var foundLogins = null;
  1037.  
  1038.         for (var i = 0; i < forms.length; i++) {
  1039.             var form = forms[i];
  1040.  
  1041.             // Only the actionOrigin might be changing, so if it's the same
  1042.             // as the last form on the page we can reuse the same logins.
  1043.             var actionOrigin = this._getActionOrigin(form);
  1044.             if (actionOrigin != previousActionOrigin) {
  1045.                 foundLogins = null;
  1046.                 previousActionOrigin = actionOrigin;
  1047.             }
  1048.             this.log("_fillDocument processing form[" + i + "]");
  1049.             foundLogins = this._fillForm(form, autofillForm, false, foundLogins)[1];
  1050.         } // foreach form
  1051.     },
  1052.  
  1053.  
  1054.     /*
  1055.      * _fillform
  1056.      *
  1057.      * Fill the form with login information if we can find it. This will find
  1058.      * an array of logins if not given any, otherwise it will use the logins
  1059.      * passed in. The logins are returned so they can be reused for
  1060.      * optimization. Success of action is also returned in format
  1061.      * [success, foundLogins]. autofillForm denotes if we should fill the form
  1062.      * in automatically, ignoreAutocomplete denotes if we should ignore
  1063.      * autocomplete=off attributes, and foundLogins is an array of nsILoginInfo
  1064.      * for optimization
  1065.      */
  1066.     _fillForm : function (form, autofillForm, ignoreAutocomplete, foundLogins) {
  1067.         // Heuristically determine what the user/pass fields are
  1068.         // We do this before checking to see if logins are stored,
  1069.         // so that the user isn't prompted for a master password
  1070.         // without need.
  1071.         var [usernameField, passwordField, ignored] =
  1072.             this._getFormFields(form, false);
  1073.  
  1074.         // Need a valid password field to do anything.
  1075.         if (passwordField == null)
  1076.             return [false, foundLogins];
  1077.  
  1078.         // If the fields are disabled or read-only, there's nothing to do.
  1079.         if (passwordField.disabled || passwordField.readOnly ||
  1080.             usernameField && (usernameField.disabled ||
  1081.                               usernameField.readOnly)) {
  1082.             this.log("not filling form, login fields disabled");
  1083.             return [false, foundLogins];
  1084.         }
  1085.  
  1086.         // Need to get a list of logins if we weren't given them
  1087.         if (foundLogins == null) {
  1088.             var formOrigin = 
  1089.                 this._getPasswordOrigin(form.ownerDocument.documentURI);
  1090.             var actionOrigin = this._getActionOrigin(form);
  1091.             foundLogins = this.findLogins({}, formOrigin, actionOrigin, null);
  1092.             this.log("found " + foundLogins.length + " matching logins.");
  1093.         } else {
  1094.             this.log("reusing logins from last form.");
  1095.         }
  1096.  
  1097.         // Discard logins which have username/password values that don't
  1098.         // fit into the fields (as specified by the maxlength attribute).
  1099.         // The user couldn't enter these values anyway, and it helps
  1100.         // with sites that have an extra PIN to be entered (bug 391514)
  1101.         var maxUsernameLen = Number.MAX_VALUE;
  1102.         var maxPasswordLen = Number.MAX_VALUE;
  1103.  
  1104.         // If attribute wasn't set, default is -1.
  1105.         if (usernameField && usernameField.maxLength >= 0)
  1106.             maxUsernameLen = usernameField.maxLength;
  1107.         if (passwordField.maxLength >= 0)
  1108.             maxPasswordLen = passwordField.maxLength;
  1109.  
  1110.         var logins = foundLogins.filter(function (l) {
  1111.                 var fit = (l.username.length <= maxUsernameLen &&
  1112.                            l.password.length <= maxPasswordLen);
  1113.                 if (!fit)
  1114.                     this.log("Ignored " + l.username + " login: won't fit");
  1115.  
  1116.                 return fit;
  1117.             }, this);
  1118.  
  1119.  
  1120.         // Nothing to do if we have no matching logins available.
  1121.         if (logins.length == 0)
  1122.             return [false, foundLogins];
  1123.  
  1124.  
  1125.         // The reason we didn't end up filling the form, if any.  We include
  1126.         // this in the formInfo object we send with the passwordmgr-found-logins
  1127.         // notification.  See the _notifyFoundLogins docs for possible values.
  1128.         var didntFillReason = null;
  1129.  
  1130.         // Attach autocomplete stuff to the username field, if we have
  1131.         // one. This is normally used to select from multiple accounts,
  1132.         // but even with one account we should refill if the user edits.
  1133.         if (usernameField)
  1134.             this._attachToInput(usernameField);
  1135.  
  1136.         // Don't clobber an existing password.
  1137.         if (passwordField.value) {
  1138.             didntFillReason = "existingPassword";
  1139.             this._notifyFoundLogins(didntFillReason, usernameField,
  1140.                                     passwordField, foundLogins, null);
  1141.             return [false, foundLogins];
  1142.         }
  1143.  
  1144.         // If the form has an autocomplete=off attribute in play, don't
  1145.         // fill in the login automatically. We check this after attaching
  1146.         // the autocomplete stuff to the username field, so the user can
  1147.         // still manually select a login to be filled in.
  1148.         var isFormDisabled = false;
  1149.         if (!ignoreAutocomplete &&
  1150.             (this._isAutocompleteDisabled(form) ||
  1151.              this._isAutocompleteDisabled(usernameField) ||
  1152.              this._isAutocompleteDisabled(passwordField))) {
  1153.  
  1154.             isFormDisabled = true;
  1155.             this.log("form not filled, has autocomplete=off");
  1156.         }
  1157.  
  1158.         // Variable such that we reduce code duplication and can be sure we
  1159.         // should be firing notifications if and only if we can fill the form.
  1160.         var selectedLogin = null;
  1161.  
  1162.         if (usernameField && usernameField.value) {
  1163.             // If username was specified in the form, only fill in the
  1164.             // password if we find a matching login.
  1165.             var username = usernameField.value.toLowerCase();
  1166.  
  1167.             let matchingLogins = logins.filter(function(l)
  1168.                                      l.username.toLowerCase() == username);
  1169.             if (matchingLogins.length) {
  1170.                 selectedLogin = matchingLogins[0];
  1171.             } else {
  1172.                 didntFillReason = "existingUsername";
  1173.                 this.log("Password not filled. None of the stored " +
  1174.                          "logins match the username already present.");
  1175.             }
  1176.         } else if (logins.length == 1) {
  1177.             selectedLogin = logins[0];
  1178.         } else {
  1179.             // We have multiple logins. Handle a special case here, for sites
  1180.             // which have a normal user+pass login *and* a password-only login
  1181.             // (eg, a PIN). Prefer the login that matches the type of the form
  1182.             // (user+pass or pass-only) when there's exactly one that matches.
  1183.             let matchingLogins;
  1184.             if (usernameField)
  1185.                 matchingLogins = logins.filter(function(l) l.username);
  1186.             else
  1187.                 matchingLogins = logins.filter(function(l) !l.username);
  1188.             if (matchingLogins.length == 1) {
  1189.                 selectedLogin = matchingLogins[0];
  1190.             } else {
  1191.                 didntFillReason = "multipleLogins";
  1192.                 this.log("Multiple logins for form, so not filling any.");
  1193.             }
  1194.         }
  1195.  
  1196.         var didFillForm = false;
  1197.         if (selectedLogin && autofillForm && !isFormDisabled) {
  1198.             // Fill the form
  1199.             if (usernameField)
  1200.                 usernameField.value = selectedLogin.username;
  1201.             passwordField.value = selectedLogin.password;
  1202.             didFillForm = true;
  1203.         } else if (selectedLogin && !autofillForm) {
  1204.             // For when autofillForm is false, but we still have the information
  1205.             // to fill a form, we notify observers.
  1206.             didntFillReason = "noAutofillForms";
  1207.             this._observerService.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
  1208.             this.log("autofillForms=false but form can be filled; notified observers");
  1209.         } else if (selectedLogin && isFormDisabled) {
  1210.             // For when autocomplete is off, but we still have the information
  1211.             // to fill a form, we notify observers.
  1212.             didntFillReason = "autocompleteOff";
  1213.             this._observerService.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
  1214.             this.log("autocomplete=off but form can be filled; notified observers");
  1215.         }
  1216.  
  1217.         this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
  1218.                                 foundLogins, selectedLogin);
  1219.  
  1220.         return [didFillForm, foundLogins];
  1221.     },
  1222.  
  1223.     /**
  1224.      * Notify observers about an attempt to fill a form that resulted in some
  1225.      * saved logins being found for the form.
  1226.      *
  1227.      * This does not get called if the login manager attempts to fill a form
  1228.      * but does not find any saved logins.  It does, however, get called when
  1229.      * the login manager does find saved logins whether or not it actually
  1230.      * fills the form with one of them.
  1231.      *
  1232.      * @param didntFillReason {String}
  1233.      *        the reason the login manager didn't fill the form, if any;
  1234.      *        if the value of this parameter is null, then the form was filled;
  1235.      *        otherwise, this parameter will be one of these values:
  1236.      *          existingUsername: the username field already contains a username
  1237.      *                            that doesn't match any stored usernames
  1238.      *          existingPassword: the password field already contains a password
  1239.      *          autocompleteOff:  autocomplete has been disabled for the form
  1240.      *                            or its username or password fields
  1241.      *          multipleLogins:   we have multiple logins for the form
  1242.      *          noAutofillForms:  the autofillForms pref is set to false
  1243.      *
  1244.      * @param usernameField   {HTMLInputElement}
  1245.      *        the username field detected by the login manager, if any;
  1246.      *        otherwise null
  1247.      *
  1248.      * @param passwordField   {HTMLInputElement}
  1249.      *        the password field detected by the login manager
  1250.      *
  1251.      * @param foundLogins     {Array}
  1252.      *        an array of nsILoginInfos that can be used to fill the form
  1253.      *
  1254.      * @param selectedLogin   {nsILoginInfo}
  1255.      *        the nsILoginInfo that was/would be used to fill the form, if any;
  1256.      *        otherwise null; whether or not it was actually used depends on
  1257.      *        the value of the didntFillReason parameter
  1258.      */
  1259.     _notifyFoundLogins : function (didntFillReason, usernameField,
  1260.                                    passwordField, foundLogins, selectedLogin) {
  1261.         // We need .setProperty(), which is a method on the original
  1262.         // nsIWritablePropertyBag. Strangley enough, nsIWritablePropertyBag2
  1263.         // doesn't inherit from that, so the additional QI is needed.
  1264.         let formInfo = Cc["@mozilla.org/hash-property-bag;1"].
  1265.                        createInstance(Ci.nsIWritablePropertyBag2).
  1266.                        QueryInterface(Ci.nsIWritablePropertyBag);
  1267.  
  1268.         formInfo.setPropertyAsACString("didntFillReason", didntFillReason);
  1269.         formInfo.setPropertyAsInterface("usernameField", usernameField);
  1270.         formInfo.setPropertyAsInterface("passwordField", passwordField);
  1271.         formInfo.setProperty("foundLogins", foundLogins.concat());
  1272.         formInfo.setPropertyAsInterface("selectedLogin", selectedLogin);
  1273.  
  1274.         this._observerService.notifyObservers(formInfo,
  1275.                                               "passwordmgr-found-logins",
  1276.                                               null);
  1277.     },
  1278.  
  1279.     /*
  1280.      * fillForm
  1281.      *
  1282.      * Fill the form with login information if we can find it.
  1283.      */
  1284.     fillForm : function (form) {
  1285.         this.log("fillForm processing form[id=" + form.id + "]");
  1286.         return this._fillForm(form, true, true, null)[0];
  1287.     },
  1288.  
  1289.  
  1290.     /*
  1291.      * _attachToInput
  1292.      *
  1293.      * Hooks up autocomplete support to a username field, to allow
  1294.      * a user editing the field to select an existing login and have
  1295.      * the password field filled in.
  1296.      */
  1297.     _attachToInput : function (element) {
  1298.         this.log("attaching autocomplete stuff");
  1299.         element.addEventListener("blur",
  1300.                                 this._domEventListener, false);
  1301.         element.addEventListener("DOMAutoComplete",
  1302.                                 this._domEventListener, false);
  1303.         this._formFillService.markAsLoginManagerField(element);
  1304.     }
  1305. }; // end of LoginManager implementation
  1306.  
  1307.  
  1308.  
  1309.  
  1310. // nsIAutoCompleteResult implementation
  1311. function UserAutoCompleteResult (aSearchString, matchingLogins) {
  1312.     function loginSort(a,b) {
  1313.         var userA = a.username.toLowerCase();
  1314.         var userB = b.username.toLowerCase();
  1315.  
  1316.         if (userA < userB)
  1317.             return -1;
  1318.  
  1319.         if (userB > userA)
  1320.             return  1;
  1321.  
  1322.         return 0;
  1323.     };
  1324.  
  1325.     this.searchString = aSearchString;
  1326.     this.logins = matchingLogins.sort(loginSort);
  1327.     this.matchCount = matchingLogins.length;
  1328.  
  1329.     if (this.matchCount > 0) {
  1330.         this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
  1331.         this.defaultIndex = 0;
  1332.     }
  1333. }
  1334.  
  1335. UserAutoCompleteResult.prototype = {
  1336.     QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
  1337.                                             Ci.nsISupportsWeakReference]),
  1338.  
  1339.     // private
  1340.     logins : null,
  1341.  
  1342.     // Interfaces from idl...
  1343.     searchString : null,
  1344.     searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
  1345.     defaultIndex : -1,
  1346.     errorDescription : "",
  1347.     matchCount : 0,
  1348.  
  1349.     getValueAt : function (index) {
  1350.         if (index < 0 || index >= this.logins.length)
  1351.             throw "Index out of range.";
  1352.  
  1353.         return this.logins[index].username;
  1354.     },
  1355.  
  1356.     getCommentAt : function (index) {
  1357.         return "";
  1358.     },
  1359.  
  1360.     getStyleAt : function (index) {
  1361.         return "";
  1362.     },
  1363.  
  1364.     getImageAt : function (index) {
  1365.         return "";
  1366.     },
  1367.  
  1368.     removeValueAt : function (index, removeFromDB) {
  1369.         if (index < 0 || index >= this.logins.length)
  1370.             throw "Index out of range.";
  1371.  
  1372.         var [removedLogin] = this.logins.splice(index, 1);
  1373.  
  1374.         this.matchCount--;
  1375.         if (this.defaultIndex > this.logins.length)
  1376.             this.defaultIndex--;
  1377.  
  1378.         if (removeFromDB) {
  1379.             var pwmgr = Cc["@mozilla.org/login-manager;1"].
  1380.                         getService(Ci.nsILoginManager);
  1381.             pwmgr.removeLogin(removedLogin);
  1382.         }
  1383.     }
  1384. };
  1385.  
  1386. var component = [LoginManager];
  1387. function NSGetModule (compMgr, fileSpec) {
  1388.     return XPCOMUtils.generateModule(component);
  1389. }
  1390.